////////////////////////////////////////////////////////
//
//  Copyright(C), 2005-2022, GEC Tech. Co., Ltd.
//  www.yueqian.com.cn  ALL RIGHT RESERVED
//
//  文件: connectingDelay.c
//  描述: TCP连接的进度和超时控制
//
///////////////////////////////////////////////////////

#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define CONNECTING 0
#define CONNECT_FAIL 1
#define CONNECT_SUCCESS 2

int g_connecting = CONNECTING;

sem_t s;


void *connecting_host(void *arg)
{
	pthread_detach(pthread_self());

	fprintf(stderr, "正在连接远端服务器");
	while(g_connecting == CONNECTING)
	{
		fprintf(stderr, ".");
		usleep(100*1000);
	}
	printf("%s", g_connecting==CONNECT_SUCCESS ? "[OK]\n": "[FAIL]\n");
	sem_post(&s);

	pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    if(argc != 2)
    {
        printf("请指定一个要连接的HTTP站点\n");
        exit(0);
    }

    // DNS查询，取得站点主机IP
	struct hostent *he = gethostbyname(argv[1]); 
	struct in_addr **addr_list = (struct in_addr **)(he->h_addr_list);

	struct sockaddr_in srvaddr;
	socklen_t addrlen = sizeof(srvaddr);
	bzero(&srvaddr, addrlen);
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port   = htons(80);
	srvaddr.sin_addr   = *addr_list[0];
    
    // 创建TCP套接字并将套接字设置为非阻塞，以便于进行连接进度控制
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	long state = fcntl(sockfd, F_GETFL);
	state |= O_NONBLOCK;
	fcntl(sockfd, F_SETFL, state);

    // 连接进度显示辅助
	sem_init(&s, 0, 0);
	g_connecting = CONNECTING;
    pthread_t t;
	pthread_create(&t, NULL, connecting_host, NULL);

    // 对服务器发起连接请求...
	if((connect(sockfd, (struct sockaddr *)&srvaddr, addrlen) == -1)
		&& (errno==EINPROGRESS || errno==EAGAIN))
	{
		fd_set wset;
		FD_ZERO(&wset);
		FD_SET(sockfd, &wset);

        // 将套接字重新设置为阻塞模式，以便于进行超时控制
		long state = fcntl(sockfd, F_GETFL);
		state &= ~O_NONBLOCK;
		fcntl(sockfd, F_SETFL, state);

		// 设置5秒超时
		struct timeval tv = {5, 0};
		int n = select(sockfd+1, NULL/*读就绪*/, &wset/*写就绪*/, NULL/*异常就绪*/, &tv);
		if(n == 0)
		{
			g_connecting = CONNECT_FAIL;
			sem_wait(&s);
			fprintf(stderr, "连接超时.\n");
			exit(0);
		}
		else if(FD_ISSET(sockfd, &wset))
		{
            int err;
            int len = sizeof(err);
            getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len);

            if(err == 0)
            {
                printf("[OK]\n");
                exit(0);
            }
			else
			{
				perror("connect error");
				exit(0);
			}
		}
	}
    else 
        printf("[OK]\n");

	exit(0);
}
